#include <iostream>

#ifdef _MSC_VER
#define __WIN32__
// 如果定义了_WIN32_这个宏,ERL_NIF_INIT 才会在展开的时候，
// 会给导出到动态库的符号 加 __declspec(dllexport)
#else
#endif


#include "erlua_wrap.h"
#include "erlua_error.h"

static ErlNifResourceType *erlua_luastate_type = nullptr;

void erlua_luastate_dtor(ErlNifEnv *caller_env, void *obj) {
    auto *lw = reinterpret_cast<erlua_wrap *>(obj);
    // std::cerr << "destruct state:" << lw->state << std::endl;
    lua_close(lw->state);
    delete[] lw->temp_buf;
}

int erlua_load(ErlNifEnv *caller_env, void **priv_data, ERL_NIF_TERM load_info) {
    // priv_data 是整个库共享的，不适宜用来存放 luaStates（也不要通过map存放在这里）
    erlua_init_global(caller_env);

    ErlDrvSysInfo erlDrv;
    enif_system_info(&erlDrv, sizeof(ErlDrvSysInfo));
    // std::cout << "load:" << caller_env << std::endl;
    if (!erlDrv.smp_support) {
        return ERLUA_NOT_SUPPORT_SMP;
    }
    auto try_flags = static_cast<ErlNifResourceFlags>(ERL_NIF_RT_CREATE | ERL_NIF_RT_TAKEOVER);
    ErlNifResourceFlags real_flags;
    erlua_luastate_type = enif_open_resource_type(caller_env, nullptr, "LuaState",
                                                  erlua_luastate_dtor, try_flags, &real_flags);
    return 0;
};

int erlua_upgrade(ErlNifEnv *caller_env, void **priv_data, void **old_priv_data, ERL_NIF_TERM load_info) {
    // unused
    return 0;
};

void erlua_unload(ErlNifEnv *caller_env, void *priv_data) {
    // unused
};

static void *l_alloc_nif (void *ud, void *ptr, size_t osize, size_t nsize) {
  (void)ud;  /* not used */
  if (nsize == 0) {
    enif_free(ptr);
    return NULL;
  }
  else
    return enif_realloc(ptr, nsize);
}

// 创建一个lua虚拟机
static ERL_NIF_TERM erlua_newstate(ErlNifEnv *env, int argc, const ERL_NIF_TERM *argv) {
    try {
        void *buf = enif_alloc_resource(erlua_luastate_type, sizeof(erlua_wrap));
        auto&& lw = static_cast<erlua_wrap *>(buf);
        lua_State *state = lua_newstate(l_alloc_nif, NULL);
        if (state == nullptr) {
            return enif_make_tuple2(env, erlua_global.error, erlua_global.allocation_fail);
        }
        luaL_openlibs(state);
        if (luaL_newmetatable(state, ERLUA_NIF_PID_META)) { // lua中申请的ErlNif可以参与lua的gc
            // 在这里加入meta方法
        }
        lw->state = state;
        lw->temp_buf = new char[ERLUA_MAX_STR_LEN];
        ERL_NIF_TERM state_term = enif_make_resource(env, lw);
        enif_release_resource(buf); // 引用计数减1, 让erlang自己管理erlua_wrap的资源
        return enif_make_tuple2(env, erlua_global.ok, state_term);
    } catch (std::exception &ex) {
        return enif_make_tuple2(env, erlua_global.exception, enif_make_string(env, ex.what(), ERL_NIF_LATIN1));
    };
};

static ERL_NIF_TERM erlua_load_luafile(ErlNifEnv *env, int argc, const ERL_NIF_TERM *argv) {
    erlua_wrap *lw = nullptr;
    try {
        if (argc != 2) {
            return enif_make_tuple2(env, erlua_global.error, erlua_global.wrong_args_nums);
        }
        lw = erlua_get_state_from_env(env, erlua_luastate_type, argv[0]);
        lw->clean();
        lw->fetch_string_from_term(env, argv[1]);
        lw->load_file();
        lw->pcall_func(0, LUA_MULTRET);
        return enif_make_tuple2(env, erlua_global.ok, lw->pop_params_to(env));
    } catch (erlua_exception &ex) {
        if (ex.ex_term != erlua_global.unknown) {
            return enif_make_tuple3(env, erlua_global.exception, enif_make_string(env, ex.what(), ERL_NIF_LATIN1),
                                    ex.ex_term);
        } else {
            return enif_make_tuple2(env, erlua_global.exception, enif_make_string(env, ex.what(), ERL_NIF_LATIN1));
        }
    } catch (std::exception &ex) {
        return enif_make_tuple2(env, erlua_global.exception, enif_make_string(env, ex.what(), ERL_NIF_LATIN1));
    }
}

static ERL_NIF_TERM erlua_memory_use(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) {
    erlua_wrap *lw = nullptr;
    try {
        if (!enif_get_resource(env, argv[0], erlua_luastate_type, reinterpret_cast<void **>(&lw))) {
            return enif_make_tuple2(env, erlua_global.error, erlua_global.not_lua_state);
        }
        int byte_used = lua_gc(lw->state, LUA_GCCOUNT, 0);
        std::cout << lua_gettop(lw->state) << std::endl;
        return enif_make_tuple2(env, erlua_global.ok, enif_make_int(env, byte_used));
    } catch (std::exception &ex) {
        return enif_make_tuple2(env, erlua_global.exception, enif_make_string(env, ex.what(), ERL_NIF_LATIN1));
    }
}

static ERL_NIF_TERM erlua_gc(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) {
    erlua_wrap *lw = nullptr;
    try {
        if (!enif_get_resource(env, argv[0], erlua_luastate_type, reinterpret_cast<void **>(&lw))) {
            return enif_make_tuple2(env, erlua_global.error, erlua_global.not_lua_state);
        }
        lua_gc(lw->state, LUA_GCCOLLECT, 0);
        return erlua_global.ok;
    } catch (std::exception &ex) {
        return enif_make_tuple2(env, erlua_global.exception, enif_make_string(env, ex.what(), ERL_NIF_LATIN1));
    }
}

static ERL_NIF_TERM erlua_func(ErlNifEnv *env, int argc, const ERL_NIF_TERM *argv) {
    erlua_wrap *lw = nullptr;
    try {
        lw = erlua_get_state_from_env(env, erlua_luastate_type, argv[0]);
        lw->clean();
        lw->fetch_string_from_term(env, argv[1]);
        lw->fetch_global_func();
        for (int i = 2; i < argc; ++i) {
            lw->push_param_from(env, argv[i]);
        }
        int arg_count = lua_gettop(lw->state) - 1;
        lw->pcall_func(arg_count, LUA_MULTRET);
        return lw->pop_params_to(env);
    } catch (erlua_exception &ex) {
        if (ex.ex_term != erlua_global.unknown) {
            return enif_make_tuple3(env, erlua_global.exception, enif_make_string(env, ex.what(), ERL_NIF_LATIN1),
                                    ex.ex_term);
        } else {
            return enif_make_tuple2(env, erlua_global.exception, enif_make_string(env, ex.what(), ERL_NIF_LATIN1));
        }
    } catch (std::exception &ex) {
        return enif_make_tuple2(env, erlua_global.exception, enif_make_string(env, ex.what(), ERL_NIF_LATIN1));
    }
}

static ERL_NIF_TERM erlua_func_ls(ErlNifEnv *env, int argc, const ERL_NIF_TERM *argv) {
    erlua_wrap *lw = nullptr;
    try {
        lw = erlua_get_state_from_env(env, erlua_luastate_type, argv[0]);
        lw->clean();
        lw->fetch_string_from_term(env, argv[1]);
        lw->fetch_global_func();
        lw->push_param_from_ls(env, argv[2]);
        int arg_count = lua_gettop(lw->state) - 1;
        lw->pcall_func(arg_count, LUA_MULTRET);
        return lw->pop_params_to(env);
    } catch (erlua_exception &ex) {
        if (ex.ex_term != erlua_global.unknown) {
            return enif_make_tuple3(env, erlua_global.exception, enif_make_string(env, ex.what(), ERL_NIF_LATIN1),
                                    ex.ex_term);
        } else {
            return enif_make_tuple2(env, erlua_global.exception, enif_make_string(env, ex.what(), ERL_NIF_LATIN1));
        }
    } catch (std::exception &ex) {
        return enif_make_tuple2(env, erlua_global.exception, enif_make_string(env, ex.what(), ERL_NIF_LATIN1));
    }
}

static ErlNifFunc nif_funcs[] = {
        {"newstate",     0,  erlua_newstate},
        {"load_luafile", 2,  erlua_load_luafile},
        {"memory_use",   1,  erlua_memory_use},
        {"gc",           1,  erlua_gc},
        {"func",         2,  erlua_func, ERL_NIF_DIRTY_JOB_CPU_BOUND},
        {"func",         3,  erlua_func, ERL_NIF_DIRTY_JOB_CPU_BOUND},
        {"func",         4,  erlua_func, ERL_NIF_DIRTY_JOB_CPU_BOUND},
        {"func",         5,  erlua_func, ERL_NIF_DIRTY_JOB_CPU_BOUND},
        {"func",         6,  erlua_func, ERL_NIF_DIRTY_JOB_CPU_BOUND},
        {"func",         7,  erlua_func, ERL_NIF_DIRTY_JOB_CPU_BOUND},
        {"func",         8,  erlua_func, ERL_NIF_DIRTY_JOB_CPU_BOUND},
        {"func",         9,  erlua_func, ERL_NIF_DIRTY_JOB_CPU_BOUND},
        {"func",         10, erlua_func, ERL_NIF_DIRTY_JOB_CPU_BOUND},
        {"func",         11, erlua_func, ERL_NIF_DIRTY_JOB_CPU_BOUND},
        {"func",         12, erlua_func, ERL_NIF_DIRTY_JOB_CPU_BOUND},
        {"func",         13, erlua_func, ERL_NIF_DIRTY_JOB_CPU_BOUND},
        {"func",         14, erlua_func, ERL_NIF_DIRTY_JOB_CPU_BOUND},
        {"func",         15, erlua_func, ERL_NIF_DIRTY_JOB_CPU_BOUND},
        {"func",         16, erlua_func, ERL_NIF_DIRTY_JOB_CPU_BOUND},
        {"func",         17, erlua_func, ERL_NIF_DIRTY_JOB_CPU_BOUND},
        {"func",         18, erlua_func, ERL_NIF_DIRTY_JOB_CPU_BOUND},
        {"func",         19, erlua_func, ERL_NIF_DIRTY_JOB_CPU_BOUND},
        {"func_ls",      3,  erlua_func_ls, ERL_NIF_DIRTY_JOB_CPU_BOUND},
};

ERL_NIF_INIT(erlua, nif_funcs, &erlua_load, NULL, &erlua_upgrade, &erlua_unload);
